package bancosys.tec.persist.span;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;

import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.utils.date.Date;

/**
 * Classe abstrata que extende {@link BaseDAO} para lidar com {@link SpanAwareBean}
 * 
 * @param <BoClass>
 */
public abstract class SpanAwareDAO<BoClass extends SpanAwareBean> extends BaseDAO<BoClass> {

    /**
     * Procura o ultimo bean, dadas as restricoes do mapa. As restricoes serao usadas em clausula eq com os nomes das propriedades iguais as
     * chaves do mapa
     * 
     * @param keys {@link Map} com os criterios de busca. se for null, age como se o mapa estivesse vazio
     * @return o bean encontrado
     * @throws BeanNotFoundException se nenhum bean for encontrado
     */
    public BoClass findLastSpan(Map<String, Serializable> keys) throws BeanNotFoundException {
        Criteria criteria = this.mountLastSpanCriteria(keys);
        criteria.add(Restrictions.eq("dataFim", SpanAwareBean.DATA_FIM));
        return this.executeSingleQuery(criteria);
    }

    /**
     * Procura o bean anterior ao passado que bate com o mapa de restricoes. Usa uma subquery.
     * <p>
     * FIXME usar orderby e pegar apenas um ao inves de usar subquery
     * 
     * @param span o bean a ser comparado
     * @param keys o mapa com as chaves. Se o mapa for null, sera tratado como se estivesse vazio.
     * @return bean
     * @throws BeanNotFoundException se nenhum bean for encontrado
     */
    public BoClass findPreviousSpan(BoClass span, Map<String, Serializable> keys) throws BeanNotFoundException {
        DetachedCriteria subcriteria = DetachedCriteria.forClass(this.getPersistableClass());
        subcriteria.setProjection(Projections.max("dataFim")).add(Restrictions.lt("dataFim", span.getDataFim()));
        Criteria criteria = this.mountLastSpanCriteria(keys);
        criteria.add(Subqueries.propertyIn("dataFim", subcriteria));
        // "dataFim in ( select max(dataFim) from A where dataFim < 'DATA_FIN' )"
        return this.executeSingleQuery(criteria);
    }

    /**
     * Devolve vigencia para data passada.
     * 
     * @param date data.
     * @return vigencia para data passada.
     * @throws BeanNotFoundException se a bean nao for encontrada
     * 
     * @deprecated este metodo pode devolver vigencias de qualquer 'dono'
     */
    public BoClass findSpanByDate(Date date) throws BeanNotFoundException {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        criteria.add(Restrictions.le("dataInicio", date));
        criteria.add(Restrictions.gt("dataFim", date));
        return this.executeSingleQuery(criteria);
    }

    /**
     * Monta a query a partir das restricoes passadas. As restricoes do mapa terao clausula eq e as chaves devem ser os nomes das
     * propriedades.
     * 
     * @param keys {@link Map} com restricoes.
     * @return {@link Criteria}
     */
    private Criteria mountLastSpanCriteria(Map<String, Serializable> keys) {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        if (keys != null) {
            for (String key : keys.keySet()) {
                criteria.add(Restrictions.eq(key, keys.get(key)));
            }
        }
        return criteria;
    }

    /**
     * {@inheritDoc}
     * 
     * @see bancosys.tec.persist.dao.BaseDAO#findAll()
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<BoClass> findAll() {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        criteria.addOrder(Order.desc("dataFim"));
        return criteria.list();
    }
}
